1. Retour sur la machine à état UML

Soit la machine à état suivante :

bonbons
Figure 1. Machine à état d’un distributeur simple

1.1. Implémentation intuitive

Implémentation sans switch case
public void insererPiece() {
  if (etat == A_PIECE) {
    System.out.println("Vous ne pouvez plus insérer de pièces");
  } else if (etat == EPUISE) {
    System.out.println("Vous ne pouvez pas insérer de pièce, nous sommes en rupture de stock");
  } else if (etat == VENDU) {
    System.out.println("Veuillez patienter, le bonbon va tomber");
  } else if (etat == SANS_PIECE) {
    etat = A_PIECE;
    System.out.println("Vous avez inséré une pièce");
  }
}

1.2. Erreur d’implémentations

  • Ce code n’adhère pas au principe Ouvert-Fermé.

  • Cette conception n’est pas orientée objet.

  • Les transitions ne sont pas explicites. Elles sont enfouies au milieu d’un tas d’instructions conditionnelles.

  • Nous n’avons pas encapsulé ce qui varie.

  • Les ajouts ultérieurs sont susceptibles de provoquer des bugs dans le code.

1.3. Une meilleure implémentation

  1. Définir une nouvelle interface Etat qui contiendra une méthode pour chaque action

  2. Implémenter une classe pour chaque Etat. Elles seront responsable du comportement.

  3. Se débarrasser de toutes les instructions conditionnelles et les remplacer par une délégation à la classe adéquate.

1.4. Illutration

Etape 1 :

bonbons
bonbons dc
Figure 2. Inplémentation des états

Etape 2

public class EtatSansPiece implements Etat {
  Distributeur distributeur;

  public EtatSansPiece(Distributeur distributeur) {
    this.distributeur = distributeur;
  }

  public void insererPiece() {
    System.out.println("Vous avez inséré une pièce");
    distributeur.setEtat(distributeur.getEtatAPiece());
  }
  ...
}

Etape 3

public class Distributeur {
  Etat etatEpuise;
  Etat etatSansPiece;
  Etat etatAPiece;
  Etat etatVendu;

  Etat etat = etatSansPiece;
  ...
  public void insererPiece() {
    etat.insererPiece();
  }
...
}

1.5. Le patron Etat

Etat permet à un objet de modifier son comportement, quand son état interne change. Tout se passe comme si l’objet changeait de classe.

etat
Figure 3. Modèle UML du patron Etat

2. Construire ses applications

Pour générer un programme, une documentation, à partir des sources, on peut :

  • Soit utiliser un environnement intégré comme eclipse

  • Soit construire les sorties (on parle de Build) à partir des sources

Nous nous intéressons dans cette section à cette deuxième catégorie.

Il existe plusieurs outils :

2.1. Les scripts

Un script shell
#!/bin/sh
UML='model.uml'
TYPE='PNG'
DOCLETPATH='/Users/bruel/dev/teaching/dut/cpoa/doclet'
PUMLPATH='/Users/bruel/dev/teaching/dut/cpoa/util'
echo "Creating $UML..."

javadoc \
-private \
-quiet \
-J-DdestinationFile=$UML \
-J-DcreatePackages=false \
-J-DshowPublicMethods=true \
-J-DshowPublicConstructors=false \
-J-DshowPublicFields=true \
-doclet de.mallox.doclet.PlantUMLDoclet -docletpath $DOCLETPATH/plantUmlDoclet.jar \
-sourcepath src src/**
echo "Done."

TYPE='png'
echo "Converting $UML to $TYPE..."
java -jar $PUMLPATH/plantuml.jar \
  -config $PUMLPATH/config.cfg \
  -t $TYPE $UML
echo "Done."
Un script batch Windows
set UML=TD1.uml
set TYPE='PNG'
set DOCLETPATH=E:\IUT-S3\CPOA\TP1\SuperCanardBof
echo "Creating %UML%..."
rem javadoc -private -quiet -J-DdestinationFile=%UML% -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true -doclet de.mallox.doclet.PlantUMLDoclet -docletpath %DOCLETPATH%\plantUmlDoclet.jar src\canard\*.java
echo "Done."

javadoc -J-DdestinationFile=%UML% -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true -doclet de.mallox.doclet.PlantUMLDoclet -docletpath plantUmlDoclet.jar src\appli\*.java src\armes\*.java src\armes\impl\*.java

set TYPE='png'
echo "Converting %UML% to $TYPE..."
java -jar %DOCLETPATH%\plantuml.jar -config "%DOCLETPATH%\config.cfg" -t %TYPE% %UML%
echo "Done."
Avantages
  • Faciles

  • Rapides

  • Beaucoup d’exemples

Inconvénients
  • Pas portables sur d’autres systèmes (no comment ;-)

  • Peu lisibles

  • Peu évolutifs

2.2. make

Un Makefile pour générer ces cours
 1 #-----------------------------------------------------
 2 ICONSDIR=images/icons
 3 IMAGESDIR=images
 4 STYLE=/Users/bruel/Dropbox/Public/dev/asciidoc/stylesheets/golo-jmb.css
 5 DOCTOR=asciidoctor -a icons -a iconsdir=$(ICONSDIR) -a images=$(IMAGESDIR) -a source-highlighter=$(HIGHLIGHT)
 6 DECK=swiss
 7 EXT=asc
 8 PANDOC=pandoc
 9 OUTPUT=.
10 DEP=definitions.txt glossaire.txt refs.txt
11 #-----------------------------------------------------
12 
13 all: $(OUTPUT)/*.html
14 
15 images/%.png: images/%.plantuml
16 @echo '==> Compiling plantUML files to generate PNG'
17 java -jar plantuml.jar $<
18 
19 %.html: %.$(EXT) $(DEP)
20 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
21 $(DOCTOR) -a toc2 -b html5 -a numbered -a eleve $<
22 
23 %.deckjs.html: %.$(EXT)  $(DEP)
24 @echo '==> Compiling asciidoc files to generate Deckjs'
25 $(DOCTOR) -T /Users/bruel/dev/asciidoctor-backends/haml/deckjs/ -a slides \
26 -a data-uri -a deckjs_theme=$(DECK) \
27 -a icons -a iconsdir=$(ICONSDIR) \
28 -a images=$(IMAGESDIR) -a prof -o $@ $<
29 
30 %-sujet.html: %.$(EXT) $(DEP)
31 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
32 $(DOCTOR) -a compact -a theme=compact -b html5 -a numbered -a eleve \
33 -a data-uri $< -o $@
34 
35 %-prof.html: %.$(EXT) $(DEP)
36 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
37 $(DOCTOR) -a prof -a correction -a theme=compact -b html5 -a numbered \
38 -a data-uri $< -o $@
%.html: %.$(EXT) $(DEP)
@echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
$(DOCTOR) -a toc2 -b html5 -a numbered -a eleve $<

Exemple d’utilisation :

$ make wip.html
==> Compiling asciidoc files with Asciidoctor to generate HTML
asciidoctor -a icons -a iconsdir=images/icons -a images=images -a source-highlighter=pygments -a toc2 -b html5 -a numbered -a eleve wip.asc
...
$ make wip.html
make: 'wip.html' is up to date.

2.3. ant

build.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="main" name="EssaiBuild">
  <target name="main">
    <echo message="Version d'Ant utilisée: ${ant.version}"/>
    <javadoc doclet="de.mallox.doclet.PlantUMLDoclet"
    docletpath="plantUmlDoclet.jar"
    access="private"
    additionalparam=
    "-encoding utf-8 -J-DdestinationFile=uml.txt -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true"
    >
      <packageset dir="../src">
        <include name="**"/>
      </packageset>
    </javadoc>

    <java jar="plantuml.jar" fork="true" maxmemory="128m">
      <arg value="uml.txt"/>
    </java>
  </target>
</project>

Exemple d’utilisation :

$ ant main
Buildfile: build.xml
main:
[javadoc] Generating Javadoc
[javadoc] Javadoc execution
[javadoc] Loading source files for package pizzeriafactorysample...
[javadoc] Constructing Javadoc information...
[javadoc] PlantUMLDoclet.createPlantUml() -  start
[javadoc] open outputfile: uml.txt
[javadoc] write interfaces/ abstract classes...
[javadoc] write content...
...
[javadoc] skip association for Pizza --> java.lang.String
[javadoc] skip association for Pizza --> java.lang.String
[javadoc] skip association for Pizza --> java.lang.String
[javadoc] skip association for Pizza --> java.util.ArrayList
[javadoc] PlantUMLDoclet.createPlantUml() -  end
BUILD SUCCESSFUL
Total time: 9 seconds

Exemple d’tilisation dans eclipse pour la génération de fichier du type TD.uml :

  1. Créer un répertoire tools et mettre dedans :

    • Plantuml.jar

    • Plantumldoclet.jar

    • build.xml

  2. Faire un Click_Droit sur build.xml et chosir Run_As ▸ Ant Build …​

    Warning

    Bien choisir celui avec les points de suspensions.

    build
  3. Dans l’onglet Environment :

    • Créer une nouvelle variable de nom Path et de valeur : le répertoire de la JDK (où se trouve javadoc ou javadoc.exe)

  4. Exécuter Run

    Tip Faire Refresh dans le navigateur pour voir les 2 fichiers générés (uml.txt et uml.png).
  5. Il suffit de faire un Click_Droit sur build.xml et chosir Run_As ▸ Ant Build …​ (SANS les 3 points de suspensions) pour relancer la génération du diagramme.

2.4. Maven

Warning Maven est un outil Java.

Convention over Configuration

Exemples de conventions :

  • le code source est supposé se trouver dans ${basedir}/src/main/java

  • les différentes ressources dans ${basedir}/src/main/resources

  • les tests dans ${basedir}/src/test

  • un projet est supposé produire un fichier JAR

  • Maven suppose que vous voulez compiler en bytecode dans ${basedir}/target/classes

  • et ensuite créer votre fichier JAR distribuable dans ${basedir}/target

pom.xml
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook</groupId>
  <artifactId>my-project</artifactId>
  <version>1.0</version>
</project>

La commande :

$ mvn install
  • va traiter les ressources,

  • compiler le source,

  • exécuter les tests unitaires,

  • créer un JAR, et

  • installer ce JAR dans le dépôt local.

La commande :

$ mvn site

va créer un fichier index.html dans target/site contenant des liens vers la JavaDoc et quelques rapports sur votre code source.

Note

Pour comparer, voici l’équivalent ant :

<project name="my-project" default="dist" basedir=".">
  <description>
    simple example build file
  </description>
  <!-- set global properties for this build -->
  <property name="src" location="src/main/java"/>
  <property name="build" location="target/classes"/>
  <property name="dist"  location="target"/>

  <target name="init">
    <!-- Create the time stamp -->
    <tstamp/>
    <!-- Create the build directory structure used by compile -->
    <mkdir dir="${build}"/>
  </target>

  <target name="compile" depends="init"
    description="compile the source " >
    <!-- Compile the java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}"/>
  </target>

  <target name="dist" depends="compile"
    description="generate the distribution" >
    <!-- Create the distribution directory -->
    <mkdir dir="${dist}/lib"/>

    <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
    <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
  </target>

  <target name="clean"
    description="clean up" >
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${build}"/>
    <delete dir="${dist}"/>
  </target>
</project>

2.5. Ivy

ivy.xml
<ivy-module version="2.0">
  <info organisation="org.apache" module="hello-ivy"/>
  <dependencies>
    <dependency org="commons-lang" name="commons-lang" rev="2.0"/>
    <dependency org="commons-cli" name="commons-cli" rev="1.0"/>
  </dependencies>
</ivy-module>
build.xml
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="hello-ivy" default="run">

  ...

  <!-- =================================
  target: resolve
  ================================= -->
  <target name="resolve" description="--> retrieve dependencies with ivy">
    <ivy:retrieve />
  </target>
</project>

2.6. Graddle

Gradle combine la flexibilité de ant avec les conventions de Maven mais évite les inconvénients de XML.

build.gradle
task hello {
  doLast {
    println 'Hello world!'
  }
}
$ gradle hello
:hello
Hello world!

BUILD SUCCESSFUL

Total time: 3.486 secs

3. Le patron proxy

3.1. Le problème

On a besoin de références à un objet, qui soient plus créatives et plus sophistiquées qu’un simple pointeur.

3.2. Le patron Proxy

Procuration (Proxy) fournit à un tiers un mandataire ou un remplaçant, pour contrôler l’accès à cet objet.

proxy
Figure 4. Modèle UML du patron Proxy
proxy google
Figure 5. Proxy sur Google

3.3. Utilisations

  • Une procuration à distance fournit un représentant local d’un objet situé dans un espace adresse différent.

  • Une procuration virtuelle crée des objets lourds à la demande.

  • Une procuration de protection contrôle l’accès à l’objet original. Les procurations de protection sont utiles quand les objets doivent satisfaire différents droits d’accès.

  • Une référence intelligente est le remplaçant d’un pointeur brut, qui réalise des opérations supplémentaires, lors de l’accès à l’objet. Quelques utilisations typiques sont :

    • décompte du nombre des références faites à un objet réel, de sorte que celui-ci puisse être libéré automatiquement, dés qu’il n’y a plus de références ;

    • charger en mémoire un objet persistant quand il est référencé pour la première fois ;

    • vérifier, avant d’y accéder, que l’objet réel est verrouillé, pour être sûr qu’aucun autre objet ne pourra le changer.

3.4. Exemple concret : RMI

Remote Method Invocation

import java.rmi.*;
public interface MonService extends Remote {
  public String direBonjour() throws RemoteException;
}
import java.rmi.*;
import java.rmi.server.*;

public class MonServiceImpl extends UnicastRemoteObject implements MonService {
  public String direBonjour() {
    return "Le serveur dit 'Bonjour'";
  }
  public MonServiceImpl() throws RemoteException {}
  public static void main (String[] args) {
    try {
      MonService service = new MonServiceImpl();
      Naming.rebind("BonjourDistant", service);
    } catch(Exception ex) {
      ex.printStackTrace();
    }
  }
}
MonService service =
  (MonService) Naming.lookup("rmi://127.0.0.1/BonjourDistant");
...
service.direBonjour();

4. Le patron Itérateur

4.1. Le problème

On veut pouvoir :

  • pour accéder au contenu d’un objet d’un agrégat sans en révéler la représentation interne ;

  • pour gérer simultanément plusieurs parcours dans des agrégats d’objets ;

  • pour offrir une interface uniforme pour les parcours au travers de diverses structures agrégats (c’est-à-dire, pour permettre l’itération polymorphe).

4.2. Le patron Itérateur

Itérateur (Iterator) fournit un moyen d’accès séquentiel aux éléments d’un agrégat d’objets, sans mettre à découvert la représentation interne de celui-ci.

iterateur
Figure 6. Modèle UML du patron Itérateur
iterateur google
Figure 7. Iterateur sur Google

4.3. Exemple concret

# Saluer tout le monde
def say_hi
  if @names.nil?
    puts "..."
  elsif @names.respond_to?("each")
    # @names est une liste de noms : traitons-les uns par uns
    @names.each do |name|
      puts "Hello #{name}!"
    end
  else
    puts "Hello #{@names}!"
  end
end

5. Le patron Composite

5.1. Le problème

On veut pouvoir :

  • représenter des hiérarchies de l’individu.

  • que le client n’ait pas à se préoccuper de la différence entre "combinaisons d’objets" et "objets individuels". Les clients pourront traiter de façon uniforme tous les objets de la structure composite.

5.2. Le patron Composite

Composite permet de composer des objets en des structures arborescentes pour représenter des hiérarchies composant/composé. Permet au client de traiter d’une façon unique les objets et les combinaisons d’objets.

composite
Figure 8. Modèle UML du patron Composite
composite google
Figure 9. Composite sur Google

5.3. Exemple concret

import java.util.ArrayList;

interface Graphic {
    public void print();
}

class CompositeGraphic implements Graphic {

  private ArrayList<Graphic> mChildGraphics = new ArrayList<Graphic>();

  public void print() {
    for (Graphic graphic : mChildGraphics) {
      graphic.print();
    }
  }

  public void add(Graphic graphic) {
    mChildGraphics.add(graphic);
  }

  public void remove(Graphic graphic) {
    mChildGraphics.remove(graphic);
  }
}

5.4. un "Anti" exemple

Que pensez-vous de cette définition de Composite ?

anti composite
Figure 10. Patron abîmé composite
Tip On appelle ces modèles des "Patrons abîmés" (anti-patterns).

6. Retour sur le refactoring Banque

6.1. Le problème

refactoring1

Remplacer tous ces switch cases

continuer = true;
while (continuer) {
  AAB.afficherMenu(monAg);
  choix = lect.next();
  choix = choix.toLowerCase();
  switch (choix) {
    ...
    case "p" :
      System.out.print("Propriétaire -> ");
      nom = lect.next();
      AAB.comptesDUnPropretaire (monAg, nom);
    break;
    ...
  • Afficher une liste séparemment du switch

    //AAB.afficherMenu(monAg);
      System.out.println("Menu de " + ag.getNomAgence() + " (" + ag.getLocAgence() + ")");
      System.out.println("l - Liste des comptes de l'agence");
      ...
      System.out.println("p - voir les comptes d'un Propriétaire (par son nom)");
      ...
      System.out.print("Choix -> ");
    }
  • Tester tous les choix pour actionner la bonne option

      ...
      case "p" :
        System.out.print("Propriétaire -> ");
        nom = lect.next();
        AAB.comptesDUnPropretaire (monAg, nom);
      break;
      ...

6.2. Une solution

  • Des listes

  • Des options de menu qui encapsulent l’action à réaliser

public interface ActionList extends Action {

  public String listTitle();
  public int size();

  public boolean addAction(Action ac);
  public boolean removeAction(Action ac);

  public String[] listOfActions() ;

}
Une interface pour les options de menu
public interface Action  {

  public String actionMessage ();
  public void execute(AgenceBancaire ag);
}
Une classe concrète par option de menu
public class Action1 implements Action {

  private String lineMessage;

  ...
  public String actionMessage() {
    return this.lineMessage;
  }

  public void execute(AgenceBancaire ab) {

    ...
    ab.afficher();
  }
}
Utilisation de l’action
  action.execute(ab);
Lien entre liste et action
Action a1 = new Action1("Liste des comptes de l'agence");
Action a2 = new Action2("Voir un compte (par son numéro)");
Action a3 = new Action3(...);

ActionList al1 = new ActionListAgenceBancaire("Menu Général");

al1.addAction(a1);
al1.addAction(a2);
Lien entre liste et action : choix dans la liste
public void execute(AgenceBancaire ab) throws Exception {
  ...
  while (true) {
    this.printMenu();

    choice = this.readResponse();
    ...
    this.myMenu.get(choice).execute(ab);
    ...
La liste peut elle-même être une option de menu (une action)!
public interface ActionList extends Action {
}